package com.easymeeting.service.impl;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import com.easymeeting.entity.dto.*;
import com.easymeeting.entity.enums.*;
import com.easymeeting.entity.po.*;
import com.easymeeting.entity.query.*;
import com.easymeeting.exception.BusinessException;
import com.easymeeting.mappers.*;
import com.easymeeting.redis.RedisComponet;
import com.easymeeting.utils.JsonUtils;
import com.easymeeting.websocket.ChannelContextUtils;
import com.easymeeting.websocket.message.MessageHandler;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import com.easymeeting.entity.vo.PaginationResultVO;
import com.easymeeting.service.MeetingInfoService;
import com.easymeeting.utils.StringTools;
import org.springframework.transaction.annotation.Transactional;


/**
 * 会议信息 业务接口实现
 */
@Service("meetingInfoService")
public class MeetingInfoServiceImpl implements MeetingInfoService {

    @Resource
    private MeetingInfoMapper<MeetingInfo, MeetingInfoQuery> meetingInfoMapper;
    @Resource
    private ChannelContextUtils channelContextUtils;
    @Resource
    private MeetingMemberMapper<MeetingMember, MeetingMemberQuery> meetingMemberMapper;
    @Resource
    private RedisComponet redisComponet;
    @Resource
    private MessageHandler messageHandler;
    @Resource
    private MeetingReserveMapper<MeetingReserve, MeetingReserveQuery> meetingReserveMapper;
    @Resource
    private MeetingReserveMemberMapper<MeetingReserveMember, MeetingReserveMemberQuery> meetingReserveMemberMapper;

    @Resource
    private UserContactMapper<UserContact, UserContactQuery> userContactMapper;

    /**
     * 根据条件查询列表
     */
    @Override
    public List<MeetingInfo> findListByParam(MeetingInfoQuery param) {
        return this.meetingInfoMapper.selectList(param);
    }

    /**
     * 根据条件查询列表
     */
    @Override
    public Integer findCountByParam(MeetingInfoQuery param) {
        return this.meetingInfoMapper.selectCount(param);
    }

    /**
     * 分页查询方法
     */
    @Override
    public PaginationResultVO<MeetingInfo> findListByPage(MeetingInfoQuery param) {
        int count = this.findCountByParam(param);
        int pageSize = param.getPageSize() == null ? PageSize.SIZE15.getSize() : param.getPageSize();

        SimplePage page = new SimplePage(param.getPageNo(), count, pageSize);
        param.setSimplePage(page);
        List<MeetingInfo> list = this.findListByParam(param);
        PaginationResultVO<MeetingInfo> result = new PaginationResultVO(count, page.getPageSize(), page.getPageNo(), page.getPageTotal(), list);
        return result;
    }

    /**
     * 新增
     */
    @Override
    public Integer add(MeetingInfo bean) {
        return this.meetingInfoMapper.insert(bean);
    }

    /**
     * 批量新增
     */
    @Override
    public Integer addBatch(List<MeetingInfo> listBean) {
        if (listBean == null || listBean.isEmpty()) {
            return 0;
        }
        return this.meetingInfoMapper.insertBatch(listBean);
    }

    /**
     * 批量新增或者修改
     */
    @Override
    public Integer addOrUpdateBatch(List<MeetingInfo> listBean) {
        if (listBean == null || listBean.isEmpty()) {
            return 0;
        }
        return this.meetingInfoMapper.insertOrUpdateBatch(listBean);
    }

    /**
     * 多条件更新
     */
    @Override
    public Integer updateByParam(MeetingInfo bean, MeetingInfoQuery param) {
        StringTools.checkParam(param);
        return this.meetingInfoMapper.updateByParam(bean, param);
    }

    /**
     * 多条件删除
     */
    @Override
    public Integer deleteByParam(MeetingInfoQuery param) {
        StringTools.checkParam(param);
        return this.meetingInfoMapper.deleteByParam(param);
    }

    /**
     * 根据MeetingId获取对象
     */
    @Override
    public MeetingInfo getMeetingInfoByMeetingId(String meetingId) {
        return this.meetingInfoMapper.selectByMeetingId(meetingId);
    }

    /**
     * 根据MeetingId修改
     */
    @Override
    public Integer updateMeetingInfoByMeetingId(MeetingInfo bean, String meetingId) {
        return this.meetingInfoMapper.updateByMeetingId(bean, meetingId);
    }

    /**
     * 根据MeetingId删除
     */
    @Override
    public Integer deleteMeetingInfoByMeetingId(String meetingId) {
        return this.meetingInfoMapper.deleteByMeetingId(meetingId);
    }

    @Override
    public void quickMeeting(MeetingInfo meetingInfo, String nickName) {
        Date date = new Date();
        meetingInfo.setCreateTime(date);
        meetingInfo.setStartTime(date);
        meetingInfo.setStatus(MeetingStatusEnum.RUNING.getStatus());
        meetingInfo.setMeetingId(StringTools.getMeetingNoOrMeetingId());
        this.meetingInfoMapper.insert(meetingInfo);
    }

    //加入成员
    private void addMeetingMember(String meetingId, String userId, String nickName, Integer memberType) {
        MeetingMember meetingMember = new MeetingMember();
        meetingMember.setMeetingId(meetingId);
        meetingMember.setUserId(userId);
        meetingMember.setNickName(nickName);
        meetingMember.setLastJoinTime(new Date());
        meetingMember.setStatus(MeetingMemberStatusEnum.NORMAL.getStatus());
        meetingMember.setMemberType(memberType);
        meetingMember.setMeetingStatus(MeetingStatusEnum.RUNING.getStatus());
        this.meetingMemberMapper.insertOrUpdate(meetingMember);
    }

    private void add2Meeting(String meetingId, String userId, String nickName, Integer sex, Boolean videoOpen, Integer memberType) {
        MeetingMemberDto meetingMemberDto = new MeetingMemberDto();
        meetingMemberDto.setUserId(userId);
        meetingMemberDto.setNickName(nickName);
        meetingMemberDto.setSex(sex);
        meetingMemberDto.setOpenVideo(videoOpen);
        meetingMemberDto.setStatus(MeetingMemberStatusEnum.NORMAL.getStatus());
        meetingMemberDto.setMemberType(memberType);
        meetingMemberDto.setJoinTime(System.currentTimeMillis());
        redisComponet.add2Meeting(meetingId, meetingMemberDto);
    }

    private void checkMeetingJoin(String meetingId, String userId) {
        MeetingMemberDto meetingMemberDto = redisComponet.getMeetingMember(meetingId, userId);
        if (meetingMemberDto != null && meetingMemberDto.getStatus().equals(MeetingMemberStatusEnum.BLACKLIST.getStatus())) {
            throw new BusinessException("对不起，你已经被拉黑");
        }
    }

    @Override
    public void joinMeeting(String mettingId, String userId, String NickName, Integer sex, Boolean videoOpen) {
        if (mettingId == null) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        MeetingInfo meetingInfo = this.meetingInfoMapper.selectByMeetingId(mettingId);
        if (meetingInfo == null || meetingInfo.getStatus() != MeetingStatusEnum.RUNING.getStatus()) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        // 校验用户
        checkMeetingJoin(mettingId, userId);
        //加入成员，成员是在数据库的
        MemberTypeEnum memberTypeEnum = meetingInfo.getCreateUserId().equals(userId) ?
                MemberTypeEnum.COMPERE : MemberTypeEnum.NORMAL;
        addMeetingMember(mettingId, userId, NickName, memberTypeEnum.getType());
        //加入会议,会议是在缓存的
        add2Meeting(mettingId, userId, NickName, sex, videoOpen, memberTypeEnum.getType());
        //加入ws房间
        channelContextUtils.addMeetingRoom(meetingInfo.getMeetingId(), userId);
        //发送ws消息
        MeetingJoinDto meetingJoinDto = new MeetingJoinDto();
        meetingJoinDto.setNewMember(redisComponet.getMeetingMember(mettingId, userId));
        meetingJoinDto.setMeetingMemberList(redisComponet.getMeetingMemberList(mettingId));


        MessageSendDto messageSendDto = new MessageSendDto();
        messageSendDto.setMessageType(MessageTypeEnum.ADD_MEETING_ROOM.getType());
        messageSendDto.setMessageContent(meetingJoinDto);
        messageSendDto.setMeetingId(mettingId);
        messageSendDto.setMessageSend2Type(MessageSend2TypeEnum.GROUP.getType());
        messageHandler.sendMessage(messageSendDto);
    }

    @Override
    public String preJoinMeeting(String meetingNo, TokenUserInfoDto tokenUserInfoDto, String password) {
        String userId = tokenUserInfoDto.getUserId();
        MeetingInfoQuery meetingInfoQuery = new MeetingInfoQuery();
        meetingInfoQuery.setMeetingNo(meetingNo);
        meetingInfoQuery.setStatus(MeetingStatusEnum.RUNING.getStatus());
        meetingInfoQuery.setOrderBy("create_time desc");
        List<MeetingInfo> meetingInfoList = meetingInfoMapper.selectList(meetingInfoQuery);
        if (meetingInfoList.isEmpty()) {
            throw new BusinessException("会议不存在");
        }
        MeetingInfo meetingInfo = meetingInfoList.get(0);
        if (!MeetingStatusEnum.RUNING.getStatus().equals(meetingInfo.getStatus())) {
            throw new BusinessException("会议已结束");
        }
        if (!StringTools.isEmpty(tokenUserInfoDto.getCurrentMeetingId()) && !meetingInfo.getMeetingId().equals(tokenUserInfoDto.getCurrentMeetingId())) {
            throw new BusinessException("你有未结束的会议无法加入其他会议");
        }
        checkMeetingJoin(meetingInfo.getMeetingId(), userId);
        if (MeetingJoinTypeEnum.PASSWORD.getType().equals(meetingInfo.getJoinType()) && !meetingInfo.getJoinPassword().equals(password)) {
            throw new BusinessException("入会密码不正确");
        }
        tokenUserInfoDto.setCurrentMeetingId(meetingInfo.getMeetingId());
        redisComponet.saveTokenUserInfoDto(tokenUserInfoDto);
        return meetingInfo.getMeetingId();
    }

    @Override
    public void exitMeeting(TokenUserInfoDto tokenUserInfoDto, MeetingMemberStatusEnum meetingMemberStatusEnum) {
        String meetingId = tokenUserInfoDto.getCurrentMeetingId();
        String userId = tokenUserInfoDto.getUserId();
        if (StringTools.isEmpty(meetingId)) {
            return;
        }
        Boolean exited = redisComponet.exitMeeting(meetingId, tokenUserInfoDto.getUserId(), meetingMemberStatusEnum);
        if (!exited) {
            tokenUserInfoDto.setCurrentMeetingId(null);
            redisComponet.saveTokenUserInfoDto(tokenUserInfoDto);
            return;
        }
        MessageSendDto messageSendDto = new MessageSendDto();
        messageSendDto.setMessageType(MessageTypeEnum.EXIT_MEETING_ROOM.getType());
        //清空会议
        tokenUserInfoDto.setCurrentMeetingId(null);
        redisComponet.saveTokenUserInfoDto(tokenUserInfoDto);
        //退出会议
        List<MeetingMemberDto> meetingMemberList = redisComponet.getMeetingMemberList(meetingId);
        MeetingExitDto meetingExitDto = new MeetingExitDto();
        meetingExitDto.setExitUserId(tokenUserInfoDto.getUserId());
        meetingExitDto.setMeetingMemberList(meetingMemberList);
        meetingExitDto.setExitStatus(meetingMemberStatusEnum.getStatus());

        messageSendDto.setMeetingId(meetingId);
        messageSendDto.setMessageSend2Type(MessageSend2TypeEnum.GROUP.getType());
        messageSendDto.setMessageContent(JsonUtils.convertObj2Json(meetingExitDto));
        messageHandler.sendMessage(messageSendDto);

        List<MeetingMemberDto> onLineMemberList = meetingMemberList.stream().
                filter(meetingMemberDto -> meetingMemberDto.getStatus() == MeetingMemberStatusEnum.NORMAL.getStatus())
                .collect(Collectors.toList());
        if (onLineMemberList.isEmpty()) {
            //结束会议
            //TODO
            MeetingReserve meetingReserve = meetingReserveMapper.selectByMeetingId(meetingId);
            if (meetingReserve == null) {
                finishMeeting(meetingId, userId);
                return;
            }
            if (System.currentTimeMillis() > meetingReserve.getStartTime().getTime() + meetingReserve.getDuration()) {
                finishMeeting(meetingId, userId);
                return;
            }

        }
        //如果是拉黑
        if (ArrayUtils.contains(new Integer[]{MeetingMemberStatusEnum.KICK_OUT.getStatus(),
                MeetingMemberStatusEnum.BLACKLIST.getStatus()}, meetingMemberStatusEnum.getStatus())) {
            MeetingMember meetingMember = new MeetingMember();
            meetingMember.setMeetingStatus(meetingMemberStatusEnum.getStatus());
            meetingMemberMapper.updateByMeetingIdAndUserId(meetingMember, meetingId, userId);
        }
    }

    @Override
    public void forceExitMeeting(TokenUserInfoDto tokenUserInfoDto, String userId, MeetingMemberStatusEnum meetingMemberStatusEnum) {
        MeetingInfo meetingInfo = meetingInfoMapper.selectByMeetingId(tokenUserInfoDto.getCurrentMeetingId());
        if (!meetingInfo.getCreateUserId().equals(tokenUserInfoDto.getUserId())) {
            throw new BusinessException("你没有权限");
        }
        TokenUserInfoDto userInfoDto = redisComponet.getTokenUserInfoDtoByUserId(userId);
        this.exitMeeting(userInfoDto, meetingMemberStatusEnum);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void finishMeeting(String meetingId, String userId) {
        MeetingInfo meetingInfo = this.meetingInfoMapper.selectByMeetingId(meetingId);
        if (userId != null && !meetingInfo.getCreateUserId().equals(userId)) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        //更新会议状态为结束
        MeetingInfo updateInfo = new MeetingInfo();
        updateInfo.setStatus(MeetingStatusEnum.FINISHED.getStatus());
        updateInfo.setEndTime(new Date());
        meetingInfoMapper.updateByMeetingId(updateInfo, meetingId);

        //发送结束会议消息
        MessageSendDto messageSendDto = new MessageSendDto();
        messageSendDto.setMessageType(MessageTypeEnum.FINIS_MEETING.getType());
        messageSendDto.setMeetingId(meetingId);
        messageSendDto.setMessageSend2Type(MessageSend2TypeEnum.GROUP.getType());
        messageHandler.sendMessage(messageSendDto);

        //更新成员
        MeetingMember meetingMember = new MeetingMember();
        meetingMember.setMeetingStatus(MeetingStatusEnum.FINISHED.getStatus());
        MeetingMemberQuery meetingMemberQuery = new MeetingMemberQuery();
        meetingMemberQuery.setMeetingId(meetingId);
        meetingMemberMapper.updateByParam(meetingMember, meetingMemberQuery);

        //更新预约会议状态
        MeetingReserve updateMeetingReserve = new MeetingReserve();
        updateMeetingReserve.setMeetingId(meetingId);
        updateMeetingReserve.setStatus(MeetingReserveStatusEnum.FINISHED.getStatus());
        meetingReserveMapper.updateByMeetingId(updateMeetingReserve, meetingId);

        List<MeetingMemberDto> meetingMemberDtoList = redisComponet.getMeetingMemberList(meetingId);
        for (MeetingMemberDto meetingMemberDto : meetingMemberDtoList) {
            TokenUserInfoDto userInfoDto = this.redisComponet.getTokenUserInfoDtoByUserId(meetingMemberDto.getUserId());
            userInfoDto.setCurrentMeetingId(null);
            redisComponet.saveTokenUserInfoDto(userInfoDto);
        }
        //清除列表
        redisComponet.removeAllMeetingMember(meetingId);
    }

    @Override
    public void reserveJoinMeeting(String meetingId, TokenUserInfoDto tokenUserInfoDto, String joinPassword) {
        String userId = tokenUserInfoDto.getUserId();
        if (!StringTools.isEmpty(tokenUserInfoDto.getCurrentMeetingId())) {
            throw new BusinessException("你有未结束的会议");
        }

        checkMeetingJoin(meetingId, userId);

        //没有你就不能加入
        MeetingReserve meetingReserve = meetingReserveMapper.selectByMeetingId(meetingId);
        if (meetingReserve == null) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        MeetingReserveMember member = this.meetingReserveMemberMapper.selectByMeetingIdAndInviteUserId(meetingId, userId);
        if (member == null) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }

        if (meetingReserve.getStatus().equals(MeetingJoinTypeEnum.PASSWORD.getType())
                && !meetingReserve.getJoinPassword().equals(joinPassword)) {
            throw new BusinessException("入会密码错误");
        }

        MeetingInfo meetingInfo = this.meetingInfoMapper.selectByMeetingId(meetingId);
        if (meetingInfo == null) {
            meetingInfo = new MeetingInfo();
            meetingInfo.setMeetingName(meetingReserve.getMeetingName());
            meetingInfo.setMeetingNo(meetingReserve.getMeetingId());
            meetingInfo.setJoinType(meetingReserve.getJoinType());
            meetingInfo.setJoinPassword(meetingReserve.getJoinPassword());
            meetingInfo.setCreateUserId(meetingReserve.getCreateUserId());
            Date curDate = new Date();
            meetingInfo.setCreateTime(curDate);
            meetingInfo.setMeetingId(meetingId);
            meetingInfo.setStartTime(curDate);
            meetingInfo.setStatus(MeetingStatusEnum.RUNING.getStatus());
            this.meetingInfoMapper.insert(meetingInfo);
        }
        tokenUserInfoDto.setCurrentMeetingId(meetingId);
        redisComponet.saveTokenUserInfoDto(tokenUserInfoDto);
    }

    @Override
    public void inviteMember(TokenUserInfoDto tokenUserInfoDto, String selectContactIds) {
        String contactIds[] = selectContactIds.split(",");

        UserContactQuery userContactQuery = new UserContactQuery();
        userContactQuery.setUserId(tokenUserInfoDto.getUserId());
        userContactQuery.setStatus(UserContactStatusEnum.FRIEND.getStatus());
        List<UserContact> userContacts = userContactMapper.selectList(userContactQuery);
        List<String> contactIdList = userContacts.stream().map(UserContact::getContactId).collect(Collectors.toList());
        if (!contactIdList.containsAll(Arrays.asList(contactIds))) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        MeetingInfo meetingInfo = meetingInfoMapper.selectByMeetingId(tokenUserInfoDto.getCurrentMeetingId());

        for (String contactId : contactIds) {
            MeetingMemberDto meetingMember = redisComponet.getMeetingMember(meetingInfo.getMeetingId(), contactId);
            if (meetingMember != null && MeetingMemberStatusEnum.NORMAL.getStatus().equals(meetingMember.getStatus())) {
                //说明就在群里
                continue;
            }
            redisComponet.addInviteInfo(tokenUserInfoDto.getCurrentMeetingId(), contactId);

            MessageSendDto sendDto = new MessageSendDto();
            sendDto.setMessageType(MessageTypeEnum.INVITE_MEMBER_MEETING.getType());
            sendDto.setMessageSend2Type(MessageSend2TypeEnum.USER.getType());
            sendDto.setReceiveUserId(contactId);

            MeetingInviteDto meetingInviteDto = new MeetingInviteDto();
            meetingInviteDto.setMeetingId(meetingInfo.getMeetingId());
            meetingInviteDto.setMeetingName(meetingInfo.getMeetingName());
            meetingInviteDto.setInviteUserName(tokenUserInfoDto.getNickName());
            sendDto.setMessageContent(JsonUtils.convertObj2Json(meetingInviteDto));

            messageHandler.sendMessage(sendDto);
        }
    }

    @Override
    public void acceptInvite(TokenUserInfoDto tokenUserInfoDto, String meetingId) {
        String inviteInfo = redisComponet.getInviteInfo(tokenUserInfoDto.getUserId(), meetingId);
        if (inviteInfo == null) {
            throw new BusinessException("邀请信息已经过期");
        }
        tokenUserInfoDto.setCurrentMeetingId(meetingId);
        tokenUserInfoDto.setCurrentNickName(tokenUserInfoDto.getNickName());
        redisComponet.saveTokenUserInfoDto(tokenUserInfoDto);
    }

    @Override
    public void updateMemberOpenVideo(String currentMeetingId, String userId, Boolean openVideo) {
        MeetingMemberDto meetingMember = redisComponet.getMeetingMember(currentMeetingId, userId);
        meetingMember.setOpenVideo(openVideo);
        redisComponet.add2Meeting(currentMeetingId, meetingMember);

        MessageSendDto messageSendDto = new MessageSendDto();
        messageSendDto.setMessageType(MessageTypeEnum.MEETING_USER_VIDEO_CHANGE.getType());
        messageSendDto.setMeetingId(currentMeetingId);
        messageSendDto.setSendUserId(userId);
        messageSendDto.setMessageSend2Type(MessageSend2TypeEnum.GROUP.getType());
        messageSendDto.setMessageContent(openVideo);
        messageHandler.sendMessage(messageSendDto);
    }
}